goog.provide('mp.tools.Scheme');
goog.provide('mp.tools.SchemePlace');
//goog.exportSymbol('mp.tools.Scheme', mp.tools.Scheme);

goog.require('goog.iter');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Line');
goog.require('goog.math.RangeSet');
goog.require('goog.math.Rect');
goog.require('mp.tools.Matrix');


/**
 * Enumeration of Scheme places.
 * @enum {number}
 */
mp.tools.SchemePlace = {
  EMPTY: 0,
  TABLE: 1,
  RESIZER: 2,
  // RESIZERS: [-1 .. -n]
  NONPLACE: 3,
  PLACE: 4,
  SPECIAL: 8
};



/**
 * @param {goog.math.Matrix|Array.<Array.<number>>|goog.math.Size|number} m
 *     A matrix to copy, a 2D-array to take as a template, a size object for
 *     dimensions, or the number of rows.
 * @param {number=} opt_n Number of columns of the matrix (only applicable if
 *     the first argument is also numeric).
 * @constructor
 * @extends {mp.tools.Matrix}
 */
mp.tools.Scheme = function(m, opt_n) {
  goog.base(this, m, opt_n);
};
goog.inherits(mp.tools.Scheme, mp.tools.Matrix);


/**
 * Get indexes bounding box for value in matrix.
 * @param {number} value Number value.
 * @param {goog.structs.Map=} opt_map Map of {Array.<goog.math.Coordinate>}.
 * @return {goog.math.Box} Bounding box.
 */
mp.tools.Scheme.prototype.getBoundingBox = function(value, opt_map) {
  var map = goog.isDefAndNotNull(opt_map) ? opt_map :
      /** @type {goog.structs.Map} */ (this.getValueIdxsMap());
  return goog.math.Box.boundingBox.apply(goog.math.Box, map.get(value));
};


/**
 * Get {mp.tools.SchemePlace.RESIZER} values from the edge.
 * @param {boolean} isVert True if process vertical edge.
 * @return {Array.<number>} Indexes.
 */
mp.tools.Scheme.prototype.getResizers = function(isVert) {
  return this.getNonEmptyEdgeIdxs(mp.tools.SchemePlace.RESIZER, isVert);
};


/**
 * Get list of {mp.tools.SchemePlace.TABLE} coordinates.
 * @param {goog.structs.Map=} opt_map Map of {Array.<goog.math.Coordinate>}.
 * @return {Array.<goog.math.Coordinate>} List of coordinates.
 */
mp.tools.Scheme.prototype.getTables = function(opt_map) {
  var map = goog.isDefAndNotNull(opt_map) ? opt_map :
      /** @type {goog.structs.Map} */ (this.getValueIdxsMap());
  return map.get(mp.tools.SchemePlace.TABLE);
};


/**
 * Get list of placeholders coordinates.
 * @param {goog.structs.Map=} opt_map Map of {Array.<goog.math.Coordinate>}.
 * @return {Array.<goog.math.Coordinate>} List of coordinates.
 */
mp.tools.Scheme.prototype.getPlaceholders = function(opt_map) {
  var map = goog.isDefAndNotNull(opt_map) ? opt_map :
      /** @type {goog.structs.Map} */ (this.getValueIdxsMap()),
      _phvalues = [
        mp.tools.SchemePlace.EMPTY,
        mp.tools.SchemePlace.RESIZER,
        mp.tools.SchemePlace.SPECIAL],
      _phs = [],
      keys = /** @type {!Array.<string>} */ (map.getKeys());

  for (var kidx = keys.length - 1; kidx >= 0; kidx--) {
    if (goog.array.contains(_phvalues, parseInt(keys[kidx])) ||
        parseInt(keys[kidx]) < 0) {
      var _list = (_list = map.get(keys[kidx])) || [];
      for (var i = _list.length - 1; i >= 0; i--) {
        var _cell = _list[i],
            _nbrs = /** @type {!Array.<number>} */
                (this.getNeighbours(_cell.y, _cell.x));
        if (goog.array.contains(_nbrs, mp.tools.SchemePlace.TABLE))
          _phs.push(_cell);
      }
    }
  }
  return _phs;
};


/**
 * Get list of rects for value in matrix.
 * @param {number} value Number value.
 * @return {Array.<goog.math.Rect>} List of rects.
 */
mp.tools.Scheme.prototype.getRects = function(value) {
  var rangesets = [],
      _h = this.height(),
      _w = this.width(),
      _rects = [];

  // fill rangeset list for every row
  for (var r = _h - 1; r >= 0; r--) {
    var _rangeset = new goog.math.RangeSet();
    for (var c = _w - 1; c >= 0; c--) {
      var _val = this.getValueAt(r, c);
      if (_val == value) {
        _rangeset.add(new goog.math.Range(c, c + 1));
      }
    }
    rangesets.push(_rangeset);
  }

  // fill rects list
  for (var rh = 1; rh < _h + 1; rh++) {
    for (var i = rangesets.length - 1; i >= 0; i--) {
      var r = rangesets.length - 1 - i,
          _rangeset = goog.iter.toArray(rangesets[i]);
      var len = _rangeset.length;
      for (var idx = 0; idx < len; idx++) {
        var _range = _rangeset[idx],
            _rect = new goog.math.Rect(
            _range.start, r,
            _range.end - _range.start, rh
            );
        _rects.push(_rect);
      }

      if (i > 0)
        rangesets[i] = rangesets[i].intersection(rangesets[i - 1]);
      else
        rangesets[i].clear();
    }
  }

  // filter rects
  var delindexes = [];
  for (var i = _rects.length - 1; i > 0; i--) {
    for (var j = i - 1; j >= 0; j--) {
      if (_rects[i].contains(_rects[j]))
        delindexes.push(j);
    }
  }

  // fill result rects list
  var resrects = [];
  for (var i = _rects.length - 1; i >= 0; i--) {
    if (!goog.array.contains(delindexes, i))
      resrects.push(_rects[i]);
  }

  return resrects;
};


/**
 * Get biggest bounding rect.
 * @param {number} value Number value.
 * @return {goog.math.Rect} Rect.
 */
mp.tools.Scheme.prototype.getBiggestRect = function(value) {
  var rects = this.getRects(value),
      rect = null,
      width = 0;
  for (var i = rects.length - 1; i >= 0; i--) {
    if (rects[i].width > width) {
      width = rects[i].width;
      rect = rects[i];
    }
  }
  return rect;
};


/**
 * Get countour paths.
 * @param {number} value Number value.
 * @return {Array.<Array.<goog.math.Line>>} Paths.
 */
mp.tools.Scheme.prototype.getPaths = function(value) {
  var lines = [],
      _h = this.height(),
      _w = this.width();

  // find all direction lines
  for (var r = -1; r < _h; r++) {
    for (var c = -1; c < _w; c++) {
      var cell = this.getValueAt(r, c),
          cR = this.getValueAt(r, c + 1),
          cB = this.getValueAt(r + 1, c);

      if (cell !== cR && cell === value)
        lines.push(new goog.math.Line(c + 1, r, c + 1, r + 1));
      else if (cell !== cR && cR === value)
        lines.push(new goog.math.Line(c + 1, r + 1, c + 1, r));

      if (cell !== cB && cell === value)
        lines.push(new goog.math.Line(c + 1, r + 1, c, r + 1));
      else if (cell !== cB && cB === value)
        lines.push(new goog.math.Line(c, r + 1, c + 1, r + 1));
    }
  }

  // find paths
  var paths = [],
      curLine = null,
      count = -1;
  while (lines.length) {
    if (curLine == null) {
      curLine = lines[0];
      goog.array.removeAt(lines, 0);
      paths.push([]);
      count++;
    }

    paths[count].push(curLine);

    for (var i = lines.length - 1; i >= 0; i--) {
      if (curLine.x1 == lines[i].x0 && curLine.y1 == lines[i].y0) {
        if (curLine.x0 == curLine.x1 && curLine.x1 == lines[i].x1 ||
            curLine.y0 == curLine.y1 && curLine.y1 == lines[i].y1) {
          curLine.x1 = lines[i].x1;
          curLine.y1 = lines[i].y1;
          goog.array.removeAt(lines, i);
          i = lines.length;
        } else {
          curLine = lines[i];
          goog.array.removeAt(lines, i);
          break;
        }
      }
    }

    if (goog.array.contains(paths[count], curLine))
      curLine = null;
  }

  return paths;
};


/**
 * Resize scheme.
 * @param {number} size Size value.
 * @param {boolean} isVert True if process vertical.
 */
mp.tools.Scheme.prototype.resizeScheme = function(size, isVert) {
  var line = this.line(0, isVert);

  if (line.length != size) {
    var _indexes = [], _lastSizers = [];

    // get all RESIZERS
    _indexes = this.getResizers(isVert);
    for (var i = 0; i < line.length; i++) {
      if (line[i] < 0)
        _lastSizers.push(line[i]);
    }

    var _minSizer = Math.min.apply(Math, goog.array.extend([-1], _lastSizers));
    var _offset = 0;
    if (line.length > size && _lastSizers.length > 0) {
      // expand scheme
      for (var i = 0; i < line.length; i++) {
        if (line[i] == _minSizer) {
          this.removeLine(i + _offset, !isVert);
          _offset--;
        }
      }
    } else if (line.length < size) {
      // collapse scheme
      for (var i = 0; i < _indexes.length; i++) {
        var _lineCpy = this.line(_indexes[i] + _offset, !isVert);
        // Set RESIZERS for new line
        _lineCpy[0] = _lineCpy[_lineCpy.length - 1] = _minSizer - 1;
        this.insertLine(_indexes[i] + 1 + _offset, _lineCpy, !isVert);
        _offset++;
      }
    }
  }
};
